• Jump To … +
    main.js separate.js single.js web-apg-api.js main.js web-conv-api.js ast.js csv.js dangling-else.js display.js flags.js float.js limits.js main.js multiline-mode.js recursive.js replace.js rules.js split.js testonly.js trace.js udt.js unicode.js web-email.js word-boundaries.js main.js phone-number.js web-main.js web-phone-number.js main.js phone-number.js setup.js translate.js xml.js branch-fail-grammar.js main.js parent-mode-grammar.js setup.js universal-mode-grammar.js colors-app.js colors-callbacks.js colors.js main.js more-app.js more-setup.js more.js ast-callbacks.js bad-input.js basic.js ini-file.js main.js parser-callbacks.js setup.js trace.js anbncn.js and.js c-comment.js compound.js main.js nested.js not.js setup.js boundaries-grammar.js boundaries.js comment-grammar.js comment.js main.js negative-grammar.js negative.js positive-grammar.js positive.js setup.js main.js odata-grammar.js run.js setup.js area-code.js lookaround.js main.js phone-number.js setup.js simple.js all-operators.js default.js fancy-number.js limited-lines.js main.js select-operators.js select-rules.js setup.js main.js minimal.js parent-u.js parent.js phone-number.js setup.js stats.js trace.js universal-u.js universal.js callbacks.js grammar.js main.js parser.js writeHtml.js LICENSE.md README.md index.md
  • rules.js

  • §
    /* eslint-disable guard-for-in */
    /* eslint-disable no-restricted-syntax */
    /* eslint-disable new-cap */
    /*  *************************************************************************************
     *   copyright: Copyright (c) 2021 Lowell D. Thomas, all rights reserved
     *     license: BSD-2-Clause (https://opensource.org/licenses/BSD-2-Clause)
     *   ********************************************************************************* */
  • §

    This module demonstrates how to deal with the phrases matched to the SABNF syntax rule names. Rule names are similar to named groupings in regex expressions. They associate a name with a phrase. apg-exp provides a little more information about the rule phrases than does the JavaScript RegExp object. Firstly, the JavaScript RegExp object does not provide for naming the grouped phrases, although other flavors of regex engines do. Secondly, the JavaScript RegExp object only gives the last match to the group and doesn’t provide the character index where it was found. apg-exp retains all phrases matched by any rule name and provides character index where it was found.

    The result object retains an array of all of the phrases found for each rule name. It is an array of phrase objects, each object having the phrase and index in the form {phrase: string, index : number}. By default, all rule names in the grammar are retained in the result object but, as will be demonstrated here, uninteresting rules or phrases can be ignored.

    The MDN description of the RegExp object also indicates that it retains “last match” information. Although most of that information seems to be missing in the node.js implementation, all of it, including the aliases, are defined and retained by the apg-exp object. This will also be demonstrated here.

    We will pick a grammar and string that will have several named-rules, some with zero, one and more matching phrases. This is a simplified ini file format. It simply consists of a single, optional section name line and one required key/pair line. Because of the many line end characters, we will use HTML display of the results. The line end characters leave many confusing gaps in the console output.

    (function rules() {
      try {
        const apgJs = require('apg-js');
        const writeHtml = require('../writeHtml');
        const grammar = new (require('./grammars/ini'))();
    
        const { apgExp } = apgJs;
        const { apgLib } = apgJs;
        let exp;
        let result;
        let str;
        let html;
        let page;
        let htmlName;
        const flags = '';
        exp = new apgExp(grammar, flags);
        console.log();
        console.log('Demonstrate named matched phrases.');
        console.log();
        console.log('SABNF grammar:');
        console.log(exp.sourceToText());
        str = '';
        str += '; comment\n';
        str += 'input = 1000\n';
        result = exp.exec(str);
  • §

    A few things to note about the output.

    • The first rule name in the grammar, called the start rule (ini in this case,) is always the same as result[0]. result[0] is simply an alias to match the familiar JavaScript RegExp result. Often, the matched phrase is all that you want and result[0] is a convenient handle to it.
    • Not all rules match a phrase. In this case, there is no section name.
    • Finally, some rules are matched to multiple phrases. In fact, the alpha and digit phrases are matched so often that they are a nuisance. We will demonstrate shortly how to get rid of the clutter.
        page = result.toHtmlPage();
        htmlName = 'ini-first-result';
        writeHtml(page, htmlName);
  • §

    Let’s say we want to see everything except the alpha, digit and owsp phrases. We can exclude those with a call to the exp.exclude() function.

        exp.exclude(['alpha', 'digit', 'owsp']);
        result = exp.exec(str);
        page = result.toHtmlPage();
        htmlName = 'ini-exclude-result';
        writeHtml(page, htmlName);
  • §

    Or we can do it the other way around with the exp.include() function. Say that we only want to see the section-name, key and value.

        exp.include(['section-name', 'key', 'value']);
        result = exp.exec(str);
        page = result.toHtmlPage();
        htmlName = 'ini-include-result';
        writeHtml(page, htmlName);
  • §

    Now that we’ve show the display of the rules (see display.js for more on display functions) let’s take a look at how to handle them programmatically. Here is a general loop that will give specific access to all of the included rule phrases.

        str = '';
        str += '; comment\n';
        str += 'input = 1000\n';
        exp = new apgExp(grammar, flags);
        exp.exclude(['alpha', 'digit']);
        result = exp.exec(str);
        html = '';
        html += exp.sourceToHtml();
        html += '<h3>input string</h3>\n';
        html += apgLib.utils.stringToAsciiHtml(str);
        html += '<h3>result</h3>\n';
        html += '<pre>\n';
  • §

    Enumerate all of the named rules.

        for (const name in result.rules) {
          if (result.rules[name]) {
  • §

    The named rule is defined. Therefore, it is an array of phrase objects. Each phrase and index is specifically identified and displayed in this loop.

            for (let i = 0; i < result.rules[name].length; i += 1) {
              const { phrase } = result.rules[name][i];
              const { index } = result.rules[name][i];
              html += `result.rules[${name}][${i}].phrase(${index}) = '`;
              html += apgLib.utils.stringToAsciiHtml(phrase);
              html += "'\n";
            }
          } else {
  • §

    Otherwise, no phrase was matched for this rule

            html += `result.rules[${name}] = undefined\n`;
          }
        }
        html += '</pre>\n';
        html = apgLib.utils.htmlToPage(html);
        htmlName = 'ini-enumeration';
        writeHtml(html, htmlName);
  • §

    Briefly, we will also show here the last match information retained in the apg-exp object.

        html = exp.toHtml();
        html = apgLib.utils.htmlToPage(html);
        htmlName = 'ini-last-match';
        writeHtml(html, htmlName);
  • §

    Programmatically, we can access the last match data similarly. A partial display is given here.

        html = '<h3>last match</h3>\n';
        html += 'exp.leftContext: ';
        html += apgLib.utils.stringToAsciiHtml(exp.leftContext);
  • §

    Enumerate the last match to each rule

        html += '<pre>\n';
        for (const name in exp.rules) {
          html += `exp.rules[${name}]: ${exp.rules[name]}`;
          html += '\n';
          const lastMatchPhraseName = `\${${name}}`;
          html += `exp[${lastMatchPhraseName}]: ${exp[lastMatchPhraseName]}`;
          html += '\n';
        }
        html += '</pre>\n';
        html = apgLib.utils.htmlToPage(html);
        htmlName = 'ini-last-match-program';
        writeHtml(html, htmlName);
      } catch (e) {
        console.log(`EXCEPTION: ${e.message}`);
      }
    })();